﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using DiscoveryLogic.Analytics.BusinessLogic.Entities;
using DiscoveryLogic.Analytics.DataObjects;
using DiscoveryLogic.Analytics.DataObjects.Networks;
using DiscoveryLogic.Analytics.DataObjects.Rules;
using DiscoveryLogic.Common.Utilities;

namespace DiscoveryLogic.Analytics.BusinessLogic.Links
{
    public class EntityRelationWorker
    {
        #region fields
        private Network _NewNetwork;
        private EntityRelationRule _Rule;
        private DataTable _SrcDataTable;
        #endregion

        #region ctor
        /// <summary>
        /// 
        /// </summary>
        /// <param name="rule"></param>
        /// <param name="srcDataSetName"></param>
        /// <param name="newNetworkName"></param>
        public EntityRelationWorker(
            EntityRelationRule rule, string srcDataSetName, 
            string newNetworkName)
        {
            this._Rule = rule;
            List<EntityBase> srcEntities = DataSetManager.ReadEntitySetData(rule.SrcEntityType, srcDataSetName);
            this._SrcDataTable =
                EntityManager.ToTable(rule.SrcEntityType, srcEntities);
            this._NewNetwork = new Network(newNetworkName, srcEntities, new List<Link>());
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="rule"></param>
        /// <param name="srcNetwork"></param>
        /// <param name="srcKeys"></param>
        public EntityRelationWorker(
            EntityRelationRule rule, 
            Network srcNetwork, List<string> srcKeys)
        {
            this._Rule = rule;
            this._NewNetwork = srcNetwork;
            this._SrcDataTable = new DataTable();
            List<EntityBase> selectedEntities=new List<EntityBase>();
            foreach(EntityBase entity in this._NewNetwork.Records)
            {
                if(entity.EntityType==rule.SrcEntityType && srcKeys.Contains(entity.PKValue()))
                {
                    selectedEntities.Add(entity);
                }
            }
            this._SrcDataTable = EntityManager.ToTable(rule.SrcEntityType, selectedEntities);
        }
        #endregion

        #region new network
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public Network ReadRelatedData()
        {
            Dictionary<string, EntityBase> newEntities = new Dictionary<string, EntityBase>();
            List<Link> newLinks = new List<Link>();

            try
            {
                List<string> sqlParamNames = new List<string>();
                Regex paramRegex = new Regex(@"@([^\s,=\)]+)", RegexOptions.IgnoreCase);
                Match paramMatch = paramRegex.Match(this._Rule.Sql);
                while (paramMatch.Success)
                {
                    string paramName = paramMatch.Groups[1].Value;
                    if (!sqlParamNames.Contains(paramName))
                        sqlParamNames.Add(paramName);

                    paramMatch = paramMatch.NextMatch();
                }
                Type srcType = EntityTypeUtil.GetType(this._Rule.SrcEntityType);
                PropertyInfo[] srcProps = EntityTypeUtil.GetProperties(this._Rule.SrcEntityType);
                
                Dictionary<string, PropertyInfo> srcPropsByName = new Dictionary<string, PropertyInfo>();
                foreach (PropertyInfo prop in srcProps)
                {
                    srcPropsByName.Add(prop.Name, prop);
                }
                Type tgtType = EntityTypeUtil.GetType(this._Rule.TgtEntityType);
                PropertyInfo[] tgtProps = EntityTypeUtil.GetProperties(this._Rule.TgtEntityType);
                Dictionary<string, PropertyInfo> tgtPropsByName = new Dictionary<string, PropertyInfo>();
                foreach (PropertyInfo prop in tgtProps)
                {
                    tgtPropsByName.Add(prop.Name, prop);
                }

                foreach (DataRow drSrc in this._SrcDataTable.Rows)
                {
                    EntityBase srcEntity = (EntityBase) Activator.CreateInstance(srcType);
                    string sql = this._Rule.Sql;

                    for (int i = 0; i < this._SrcDataTable.Columns.Count; i++)
                    {
                        string colName = this._SrcDataTable.Columns[i].ColumnName;
                        if (!this._Rule.FieldNameMappings.ContainsValue(colName))
                            continue;
                        object fieldValue = drSrc[colName];
                        if (fieldValue != null && fieldValue != DBNull.Value)
                        {
                            foreach (string propName in this._Rule.FieldNameMappings.Keys)
                            {
                                if (this._Rule.FieldNameMappings[propName] == colName)
                                {
                                    if (drSrc[colName] != null && drSrc[colName] != DBNull.Value)
                                    {
                                        PropertyInfo prop = srcPropsByName[propName];
                                        if (prop.CanWrite)
                                        {
                                            SimpleDataType dbType3 =
                                                SimpleDataTypeUtil.ToSimpleDataType(prop.PropertyType);
                                            fieldValue = SimpleDataTypeUtil.ReadDbValue(dbType3, fieldValue);
                                            prop.SetValue(srcEntity, fieldValue, null);
                                        }
                                    }
                                }
                            }
                        }

                        foreach (string paramName in this._Rule.SqlParameters.Keys)
                        {
                            string propName = this._Rule.SqlParameters[paramName];
                            if (this._Rule.FieldNameMappings[propName] == colName)
                            {
                                SqlDbType dbType = this._Rule.ParameterTypes[paramName];
                                SimpleDataType dbType2 = SimpleDataTypeUtil.ToSimpleDataType(dbType);
                                fieldValue = SimpleDataTypeUtil.ReadDbValue(dbType2, fieldValue);

                                if (fieldValue == null)
                                {
                                    sql = sql.Replace("@" + paramName, "null");
                                }
                                //else if (this._Rule.FieldTransformations.ContainsKey(propName))
                                //{
                                //    object transformedValue = fieldValue;
                                //    string sqlValue = FieldDataType.ToMssqlDataValue(
                                //        dbType, transformedValue.ToString());
                                //    sql = sql.Replace("@" + paramName, sqlValue);
                                //}
                                else
                                {
                                    string sqlValue = FieldDataType.ToMssqlDataValue(dbType, fieldValue.ToString());
                                    sql = sql.Replace("@" + paramName, sqlValue);
                                }
                            }
                        }
                    }

                    DataTable dtOutput = DbUtil.GetTableDataBySQL(this._Rule.ConnStr, this._Rule.TgtEntityType.ToString(), sql);
                    if (dtOutput != null && dtOutput.Rows.Count > 0)
                    {
                        Dictionary<EntityBase, Range<DateTime>> relatedEntities =
                            new Dictionary<EntityBase, Range<DateTime>>();

                        foreach (DataRow drTgt in dtOutput.Rows)
                        {
                            Range<DateTime> timeRange = new Range<DateTime>(
                                new DateTime(1900, 1, 1),
                                new DateTime(2099, 12, 31));
                            if(!string.IsNullOrEmpty(this._Rule.TimeStampField) && 
                                drTgt[this._Rule.TimeStampField]!=null)
                            {
                                DateTime dt= DateTime.Now;
                                if(DateTime.TryParse(drTgt[this._Rule.TimeStampField].ToString(), out dt))
                                {
                                    timeRange.FromValue = new DateTime(dt.Year, 1, 1);
                                    timeRange.ToValue=new DateTime(dt.Year, 12,31);
                                }
                            }
                            EntityBase tgtEntity = (EntityBase) Activator.CreateInstance(tgtType);
                            foreach(string propName in this._Rule.OutputFieldNameMappings.Keys)
                            {
                                string colName = this._Rule.OutputFieldNameMappings[propName];
                                object fieldValue = drTgt[colName];
                                if (fieldValue != null && fieldValue != DBNull.Value)
                                {
                                    PropertyInfo prop = tgtPropsByName[propName];
                                    SimpleDataType dbType = SimpleDataTypeUtil.ToSimpleDataType(prop.PropertyType);
                                    fieldValue = SimpleDataTypeUtil.ReadDbValue(dbType, fieldValue);
                                    tgtPropsByName[propName].SetValue(tgtEntity, fieldValue, null);
                                }
                            }
                            if(!this._Rule.OutputFieldNameMappings.ContainsKey(tgtEntity.IDField))
                            {
                                PropertyInfo prop = tgtPropsByName[tgtEntity.IDField];
                                SqlDbType dbType = SqlDbTypeUtil.ToSqlDbType(prop.PropertyType);
                                if(FieldDataType.IsNumber(dbType))
                                {
                                    int randomNumber = tgtEntity.GetHashCode();
                                    prop.SetValue(tgtEntity,randomNumber,null);
                                }
                                else if(FieldDataType.IsStringType(dbType))
                                {
                                    string guid = Guid.NewGuid().ToString();
                                    prop.SetValue(tgtEntity, guid,null);
                                }
                            }
                            
                            relatedEntities.Add(tgtEntity, timeRange);
                        }

                        List<Link> links = this.FilterLinkedEntities(
                            srcType, tgtType, srcEntity, ref relatedEntities);
                        if (relatedEntities != null && relatedEntities.Count > 0 && 
                            links != null && links.Count > 0)
                        {
                            foreach(EntityBase relatedEntity in relatedEntities.Keys)
                            {
                                if(!newEntities.ContainsKey(relatedEntity.PKValue()))
                                    newEntities.Add(relatedEntity.PKValue(), relatedEntity);
                            }
                            newLinks.AddRange(links);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                ExceptionHandler.HandlesException(ex);
                throw new Exception("Unable to read related entities: " + ex.ToString());
            }

            if (newEntities == null || newEntities.Count == 0)
                return this._NewNetwork;

            List<string> addedEntityKeys=new List<string>();
            foreach(EntityBase newEntity in newEntities.Values)
            {
                bool canAdd = true;
                foreach(EntityBase entity in this._NewNetwork.Records)
                {
                    if(entity.EntityType==newEntity.EntityType && entity.PKValue()==newEntity.PKValue())
                    {
                        canAdd = false;
                        break;
                    }
                }
                if(canAdd)
                {
                    this._NewNetwork.Records.Add(newEntity);
                    addedEntityKeys.Add(newEntity.PKValue());
                }
            }
            foreach(Link newLink in newLinks)
            {
                if(addedEntityKeys.Contains(newLink.ToKey))
                    this._NewNetwork.Links.Add(newLink);
            }

            // newNetwork=new Network();

            return this._NewNetwork;
        }
        #endregion

        #region distance
        private List<Link> FilterLinkedEntities(
            Type srcType, Type tgtType,
            EntityBase srcEntity, 
            ref Dictionary<EntityBase, Range<DateTime>> relatedEntities)
        {
            List<Link> links = new List<Link>();
            if (relatedEntities != null && relatedEntities.Count > 0)
            {
                int idx = 0;
                Assembly assembly = typeof (IEntityLinker).Assembly;
                Type linkerCalcType = assembly.GetType(this._Rule.EntityLinkerName);
                IEntityLinker calc =
                    (IEntityLinker)Activator.CreateInstance(linkerCalcType);
                calc.InitBindings(srcType, tgtType,
                    this._Rule.LinkCalcArgumentMappings);
                while (idx < relatedEntities.Count)
                {
                    EntityBase entity = relatedEntities.Keys.ToList()[idx];
                    Link link = new Link(
                        srcType, tgtType,
                        srcEntity.PKValue(), entity.PKValue(),
                        double.PositiveInfinity, 1, this._Rule.LinkType, this._Rule.Directional);
                    double? distance = calc.CalculateDistance(srcEntity, entity);
                    if (distance.HasValue && distance <= this._Rule.LinkDistanceThreshold)
                    {
                        link.Distance = distance.Value;
                        link.LinkTimeSpan = relatedEntities[entity];
                        links.Add(link);
                        idx++;
                    }
                    else
                    {
                        relatedEntities.Remove(entity);
                    }
                }
            }

            

            return links;
        }
        #endregion
    }
}
